﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

using Monti.DragAndDrop;
using Monti.Utilities;

namespace Monti.Docking
{
   /// <summary>
   /// Dock site that hosts dock content.
   /// </summary>
   internal class DockSite : TabControl, INotifyPropertyChanged
   {
      #region Fields

      /// <summary>
      /// Dock Manager.
      /// </summary>
      private DockManager m_DockMan;

      /// <summary>
      /// Occurs when a dock content is requesting to close.
      /// </summary>
      public event EventHandler<DockContentCancelEventArgs> DockContentClosing;

      /// <summary>
      /// Occurs when a dock content has closed.
      /// </summary>
      public event EventHandler<DockContentEventArgs> DockContentClosed;

      #endregion

      #region Setup

      /// <summary>
      /// Static Constructor.
      /// </summary>
      static DockSite ()
      {
         DragHandler = new DockingDragController ();
         DropHandler = new DockingDropController ();
         DefaultStyleKeyProperty.OverrideMetadata(typeof(DockSite),
                                                  new FrameworkPropertyMetadata(typeof(DockSite)));
         TabStripPlacementProperty.AddOwner(typeof(DockSite), new FrameworkPropertyMetadata(Dock.Top));
      }

      /// <summary>
      /// Initializes a new dock site object.
      /// </summary>
      public DockSite ()
      {
         this.Loaded += this.DockSiteLoaded;
         this.PreviewMouseDown += DockSitePreviewMouseDown;
         DragNDrop.SetIsDragSource (this, true);
         DragNDrop.SetIsDropTarget (this, true);
         DragNDrop.SetDragController (this, DragHandler);
         DragNDrop.SetDropController (this, DropHandler);
      }

      /// <summary>
      /// Occurs when the dock site is loaded.
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      void DockSiteLoaded (object sender, RoutedEventArgs e)
      {
         this.Loaded -= this.DockSiteLoaded;
         if (this.DockMan == null) GetDockMan();
      }

      /// <summary>
      /// Gets the host dock manager.
      /// </summary>
      private void GetDockMan()
      {
         var dockMan = this.FindLogicalParent<DockManager> () ??
            this.FindVisualParent<DockManager> ();
         if (dockMan == null)
            throw new InvalidOperationException ("Dock site must some how be hosted in a dock manager!");
         this.DockMan = dockMan;
      }

      #endregion

      #region Properties

      /// <summary>
      /// Gets the docking drag handler.
      /// </summary>
      internal static DockingDragController DragHandler { get; private set; }

      /// <summary>
      /// Gets the docking drop handler.
      /// </summary>
      internal static DockingDropController DropHandler { get; private set; }

      /// <summary>
      /// Dependency property for if the site is a tool window or not.
      /// </summary>
      public static readonly DependencyProperty IsToolWindowProperty =
         DependencyProperty.Register ("IsToolWindow", typeof (bool), typeof (DockSite),
                                      new PropertyMetadata (default(bool)));

      /// <summary>
      /// Gets if the dock site is a tool window or not.
      /// </summary>
      public bool IsToolWindow
      {
         get { return (bool) GetValue (IsToolWindowProperty); }
         internal set { SetValue (IsToolWindowProperty, value); }
      }

      /// <summary>
      /// Dependency property for if the site has the active or not.
      /// </summary>
      public static readonly DependencyProperty HasActiveContentProperty =
         DependencyProperty.Register ("HasActiveContent", typeof (bool), typeof (DockSite),
                                      new PropertyMetadata (default(bool)));

      /// <summary>
      /// Gets or Sets if the site has the acitve content or not.
      /// </summary>
      public bool HasActiveContent
      {
         get { return (bool) GetValue (HasActiveContentProperty); }
         internal set { SetValue (HasActiveContentProperty, value); }
      }

      /// <summary>
      /// Dependency property for the number of items in this group.
      /// </summary>
      internal static readonly DependencyProperty ItemCountProperty =
         DependencyProperty.Register ("ItemCount", typeof (int), typeof (DockSite),
                                      new PropertyMetadata (0));

      /// <summary>
      /// Gets or Sets number of items in this group.
      /// </summary>
      internal int ItemCount
      {
         get { return (int) GetValue (ItemCountProperty); }
         set { SetValue (ItemCountProperty, value); }
      }

      /// <summary>
      /// Determines if new dock contents are added to the end of the list.
      /// </summary>
      public bool AddNewContentToEnd
      {
         get { return (bool)GetValue (AddNewContentToEndProperty); }
         set { SetValue (AddNewContentToEndProperty, value); }
      }

      /// <summary>
      /// Dependency property for setting if new dock contents are added to the end of a dock site's list..
      /// </summary>
      public static readonly DependencyProperty AddNewContentToEndProperty =
         DependencyProperty.Register ("AddNewContentToEnd", typeof (bool), typeof (DockSite),
                                      new UIPropertyMetadata (true));

      /// <summary>
      /// Gets or Sets the DockGroup. This is a dependency property.
      /// </summary>
      public DockGroup Group
      {
         get { return (DockGroup)GetValue (GroupProperty); }
         set { SetValue (GroupProperty, value); }
      }

      /// <summary>
      /// Dependency property for dock group.
      /// </summary>
      public static readonly DependencyProperty GroupProperty =
         DependencyProperty.Register ("Group", typeof (DockGroup), typeof (DockSite),
                                      new UIPropertyMetadata (null, GroupChanged));

      /// <summary>
      /// Gets the dock content at the given index.
      /// </summary>
      /// <param name="index"></param>
      /// <returns></returns>
      public DockContent this[int index]
      {
         get
         {
            if (index >= 0 && index < this.Items.Count)
               return this.Items[index] as DockContent;

            return null;
         }
      }

      /// <summary>
      /// Gets the owner dock manager.
      /// </summary>
      public DockManager DockMan
      {
         get
         {
            if (this.m_DockMan == null) GetDockMan();
            return this.m_DockMan;
         }
         internal set
         {
            this.m_DockMan = value;
         }
      }

      #endregion

      #region Methods

      #region Overrides

      /// <summary>
      /// Checks to see if the given item is the required container.
      /// </summary>
      /// <param name="item">Item to check.</param>
      /// <returns>True if item is dock content, otherwise false.</returns>
      protected override bool IsItemItsOwnContainerOverride (object item)
      {
         if (item == null)
            throw new InvalidOperationException ("Dock content item is null!");
         return false;
      }

      /// <summary>
      /// Gets the needed container for items in a dock site.
      /// </summary>
      /// <returns></returns>
      protected override DependencyObject GetContainerForItemOverride ()
      {
         return new DockContent { DockSite = this };
      }

      #endregion

      /// <summary>
      /// Occurs when the user clicks this dock site.
      /// </summary>
      /// <param name="sender">Object that raised the event.</param>
      /// <param name="e">MouseButtonEventArgs</param>
      void DockSitePreviewMouseDown (object sender, MouseButtonEventArgs e)
      {
         if (this.SelectedIndex == -1) return;
         var content = this.ItemContainerGenerator.ContainerFromIndex (this.SelectedIndex) as DockContent;
         if (content != null)
         {
            if (content.IsActiveContent) return;
            content.SetAsActiveContent ();
         }
      }

      /// <summary>
      /// Removes the given content from the site.
      /// </summary>
      /// <param name="content">Content to remove.</param>
      public void RemoveContent (object content)
      {
         var c = new DockContentCancelEventArgs (content);
         if (DockContentClosing != null)
            DockContentClosing (this.Group, c);
         if (c.Cancel) return;

         var dockItem = content as IDockItem;
         var okToClose = !(dockItem != null && !dockItem.Closing ());

         if (okToClose)
         {
            this.Group.Items.Remove (dockItem);
            if (DockContentClosed != null)
               DockContentClosed (this.Group, new DockContentEventArgs (content));
         }
      }

      /// <summary>
      /// Occurs when the children colleciton changes.
      /// </summary>
      /// <param name="d">Docksite that triggered event.</param>
      /// <param name="e">DependencyPropertyChangedEventArgs</param>
      private static void GroupChanged (DependencyObject d, DependencyPropertyChangedEventArgs e)
      {
         var dockSite = d as DockSite;
         if (dockSite != null)
         {
            if (BindingOperations.IsDataBound (dockSite, ItemsSourceProperty))
               BindingOperations.ClearBinding (dockSite, ItemsSourceProperty);

            dockSite.Group.DockSite = dockSite;
            var binding = new Binding ("Items") { Source = dockSite.Group };
            dockSite.SetBinding (ItemsSourceProperty, binding);
            foreach (DockGroup dockGroup in dockSite.Group.Children)
               dockSite.AddDockGroup (dockGroup);
            dockSite.Group.Items.CollectionChanged += dockSite.GroupItemsChanged;
            dockSite.Group.Children.CollectionChanged += dockSite.GroupChildrenChanged;
            dockSite.ItemCount = dockSite.Group.Items.Count;
         }
      }

      /// <summary>
      /// Occurs when the items of the group change.
      /// </summary>
      /// <param name="sender">Items collection.</param>
      /// <param name="e">NotifyCollectionChangedEventArgs</param>
      private void GroupItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
      {
         this.ItemCount = this.Group.Items.Count;
         if (e.Action == NotifyCollectionChangedAction.Add)
         {
            var dockItem = e.NewItems[0] as IDockItem;
            if (dockItem != null)
               dockItem.IsSelected = true;
         }
         if (e.Action == NotifyCollectionChangedAction.Remove && this.Group.Items.Count == 0)
         {
            /*if (this.Group.Parent == null && this.IsToolWindow)
            {
               RemoveToolWindow();
               return;
            }*/
            
            if (this.Group.Parent == null && !this.IsToolWindow) return;
            this.Group.Children.CollectionChanged -= this.GroupChildrenChanged;
            this.Group.Items.CollectionChanged -= this.GroupItemsChanged;

            var parentPanel = this.FindVisualParent<SplitPanel> ();
            parentPanel.Children.Remove (this);
            RemoveSplitter (parentPanel);
            if (parentPanel.Children.Count == 0)
            {
               var pPanel = parentPanel.FindVisualParent<SplitPanel> ();
               pPanel.Children.Remove (parentPanel);
               RemoveSplitter (pPanel);
               parentPanel = pPanel;
            }
            parentPanel.Children [0].SetValue (Grid.RowProperty, 0);
            parentPanel.Children [0].SetValue (Grid.ColumnProperty, 0);

            if (this.Group.Parent == null) return;
            this.Group.Parent.Children.Remove (this.Group);
            if (this.Group.Children.Count > 0)
            {
               var groupToMove = this.Group.Children [this.Group.Children.Count - 1];
               groupToMove.DockArea = this.Group.DockArea;
               this.Group.Parent.Children.Add (groupToMove);
               for (int a = 0; a < this.Group.Children.Count - 2; a++)
                  groupToMove.Children.Insert (0, this.Group.Children[a]);
            }
         }
      }

      /// <summary>
      /// Removes the splitter from the given panel.
      /// </summary>
      /// <param name="parentPanel">Panel to remove splitter from.</param>
      private static void RemoveSplitter (SplitPanel parentPanel)
      {
         foreach (var child in parentPanel.Children)
         {
            if (child is GridSplitter)
            {
               parentPanel.Children.Remove ((UIElement) child);
               parentPanel.RowDefinitions.Clear ();
               parentPanel.ColumnDefinitions.Clear ();
               break;
            }
         }
      }

      /// <summary>
      /// Occurs when the children of the group change.
      /// </summary>
      /// <param name="sender">Children collection.</param>
      /// <param name="e">NotifyCollectionChangedEventArgs</param>
      internal void GroupChildrenChanged (object sender, NotifyCollectionChangedEventArgs e)
      {
         if (e.Action == NotifyCollectionChangedAction.Add)
         {
            foreach (var dockGroup in this.Group.Children)
            {
               if (dockGroup.DockSite == null)
                  AddDockGroup (dockGroup);
            }
         }
      }

      /// <summary>
      /// Adds the given dock group.
      /// </summary>
      /// <param name="dockGroup"></param>
      private void AddDockGroup (DockGroup dockGroup)
      {
         var newParent = this.GetParentSplitPanel ();
         var site = new DockSite { DockMan = this.DockMan, Group = dockGroup, IsToolWindow = this.IsToolWindow };
         this.AddNewDockSite (dockGroup.DockArea, site, newParent);
      }

      internal void AddNewDockSite (DockArea dockArea, DockSite site, SplitPanel newParent)
      {
         if (dockArea == DockArea.Top)
            SetGridValues (newParent, site, 2, 0, 0, 0, dockArea);
         else if (dockArea == DockArea.Left)
            SetGridValues (newParent, site, 0, 2, 0, 0, dockArea);
         else if (dockArea == DockArea.Bottom)
            SetGridValues (newParent, site, 0, 0, 2, 0, dockArea);
         else if (dockArea == DockArea.Right)
            SetGridValues (newParent, site, 0, 0, 0, 2, dockArea);

         newParent.Children.Add (site);
      }

      internal SplitPanel GetParentSplitPanel ()
      {
         var parentPanel = this.FindVisualParent<SplitPanel> ();
         if (parentPanel == null)
            throw new InvalidOperationException ("Parent of a dock site must be a SplitPanel!");

         SplitPanel newParent;
         if (parentPanel.HasSplit)
         {
            newParent = new SplitPanel ();
            var thisRow = (int) this.GetValue (Grid.RowProperty);
            var thisCol = (int) this.GetValue (Grid.ColumnProperty);
            newParent.SetValue (Grid.RowProperty, thisRow);
            newParent.SetValue (Grid.ColumnProperty, thisCol);
            parentPanel.Children.Add (newParent);
            parentPanel.Children.Remove (this);
            newParent.Children.Add (this);
         }
         else
            newParent = parentPanel;
         return newParent;
      }

      /// <summary>
      /// Sets up the needed grid values.
      /// </summary>
      /// <param name="newGrid">New grid to setup.</param>
      /// <param name="site">New dock site to setup.</param>
      /// <param name="myRow">Row for this dock site.</param>
      /// <param name="myCol">Column for this dock site.</param>
      /// <param name="siteRow">Row for new dock site.</param>
      /// <param name="siteCol">Column for new dock site.</param>
      /// <param name="dockArea">Area we are doing to.</param>
      private void SetGridValues (SplitPanel newGrid, DockSite site,
                                  int myRow, int myCol, int siteRow, int siteCol, DockArea dockArea)
      {
         if (myRow == siteRow) newGrid.SetupHorizontalGrid (site.IsToolWindow && !this.IsToolWindow, dockArea);
         if (myCol == siteCol) newGrid.SetupVerticalGrid (site.IsToolWindow && !this.IsToolWindow, dockArea);
         this.SetValue (Grid.RowProperty, myRow);
         this.SetValue (Grid.ColumnProperty, myCol);
         site.SetValue (Grid.RowProperty, siteRow);
         site.SetValue (Grid.ColumnProperty, siteCol);
      }

      #endregion

      #region Property Changed

      /// <summary>
      /// Event used to notify when a property has changed.
      /// </summary>
      public event PropertyChangedEventHandler PropertyChanged;

      /// <summary>
      /// Fires the property changed event.
      /// </summary>
      /// <param name="propertyName">Name of property that changed.</param>
      protected void FirePropertyChange (string propertyName)
      {
         if (this.PropertyChanged != null)
            PropertyChanged (this, new PropertyChangedEventArgs (propertyName));
      }

      #endregion
   }
}
